/**
 * Copyright 2014 opencxa.org
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.cxa.rpc.cloud.server;

import java.util.concurrent.TimeUnit;

import org.cxa.commandLineParser.CommandLineParser;
import org.cxa.commandLineParser.optionListener.OptionWithArgumentListener;
import org.cxa.misc.sqLiteUtils.SqLiteDatabase.DatabaseException;
import org.cxa.rpc.cloud.server.components.authentication.DatabaseManager;
import org.cxa.rpc.cloud.server.components.authentication.SqLiteDatabaseManager;
import org.cxa.rpc.transportChannel.json.webSocket.ServerWebSocketJsonTransportChannel;
import org.cxa.timeUtils.TimeDiff;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.server.handler.ContextHandler;
import org.eclipse.jetty.server.handler.HandlerCollection;
import org.eclipse.jetty.server.handler.ResourceHandler;
import org.eclipse.jetty.websocket.server.WebSocketHandler;
import org.eclipse.jetty.websocket.servlet.WebSocketServletFactory;


/**
 * @author Christopher Armenio
 */
public class CloudServer
{
	private static final int PORT_NUM_WEBSOCKET = 2014;
	private static final int PORT_NUM_UI = 8080;
	
	private static final String VH_NAME_WEBSOCKET = "websocket";
	private static final String VH_NAME_UI = "userInterface";
	
	private static CloudServer SINGLETON = null;
	
	
	private final Server server;
	private final DatabaseManager dbManager;
	
	
	public CloudServer(DatabaseManager dbManagerIn) throws Exception
	{
		this.dbManager = dbManagerIn;
		this.server = new Server();
		
		// setup our connectors
		server.addConnector(new ServerConnector(this.server)
		{{
			setName(VH_NAME_WEBSOCKET);
			setPort(PORT_NUM_WEBSOCKET);
		}});
		server.addConnector(new ServerConnector(this.server)
		{{
			setName(VH_NAME_UI);
			setPort(PORT_NUM_UI);
		}});

		// setup our handlers
		HandlerCollection contexts = new HandlerCollection();
		contexts.addHandler(new ContextHandler()
		{{
			setContextPath("/");
			setVirtualHosts(new String[]{"@"+VH_NAME_WEBSOCKET});
			
			setHandler(new WebSocketHandler()
			{
				@Override
				public void configure(WebSocketServletFactory factory)
				{
					factory.register(ServerWebSocketJsonTransportChannel.class);
				}
			});
		}});
		contexts.addHandler(new ContextHandler()
		{{
			setContextPath("/");
			setVirtualHosts(new String[]{"@"+VH_NAME_UI});
			setHandler(new ResourceHandler()
			{{
				setDirectoriesListed(true);
				setWelcomeFiles(new String[]{"index.html"});
				setResourceBase(CloudServer.class.getClassLoader().getResource("org/cxa/rpc/cloud/server/userInterface/resources").toExternalForm());
			}});
		}});
		this.server.setHandler(contexts);
		
		SINGLETON = this;
		
		// get our IoBroker instantiated and start our server
		IoBroker.init();
		this.server.start();
	}
	
	
	public boolean waitForStart(long timeoutIn, TimeUnit timeUnitIn)
	{
		TimeDiff td_waitForStart = new TimeDiff();
		boolean retVal = false;
		while( !(retVal = this.server.isRunning()) )
		{
			if( td_waitForStart.isElapsed(timeoutIn, timeUnitIn) ) break;
			Thread.yield();
		}
		
		return retVal;
	}
	
	
	public int getNumOpenConnections_jetty()
	{
		return this.server.getConnectors()[0].getConnectedEndPoints().size();
	}
	
	
	public DatabaseManager getDatabaseManager()
	{
		return this.dbManager;
	}
	
	
	private void join() throws InterruptedException
	{
		this.server.join();
	}
	
	
	public static CloudServer getSingleton()
	{
		if( SINGLETON == null ) throw new RuntimeException("singleton was NOT initialized");
		return SINGLETON;
	}
	
	
	public static void main(String[] args)
	{	
		final SqLiteDatabaseManager DBMAN = new SqLiteDatabaseManager();
		
		CommandLineParser clp = new CommandLineParser("CloudServer");
		clp.addOption("db", "database", "database file used for authentication", false, true, String.class,
			new OptionWithArgumentListener<String>()
			{
				@Override
				public void optionIsPresent(String argIn)
				{
					try { DBMAN.openDatabase(argIn); }
					catch (DatabaseException e) { CloudServer.handleStartupError(e); }
				}
			});
		clp.addOption("er", "enableRoot", "enable the root user with the specified password", false, true, String.class,
			new OptionWithArgumentListener<String>()
			{
				@Override
				public void optionIsPresent(String argIn)
				{
					DBMAN.enableRootUser(argIn);
				}
			});
		clp.parseOptions(args);
		
		
		// try to open the default database (if one is not specified on the command line)
		if( !DBMAN.isOpen() )
		{
			try { DBMAN.openDatabase(SqLiteDatabaseManager.DEFAULT_DB); }
			catch (DatabaseException e) { CloudServer.handleStartupError(e); }
		}
		
		// start the cloud server!
		try
		{
			CloudServer cs = new CloudServer(DBMAN);
			cs.join();
		}
		catch (Exception e) { CloudServer.handleStartupError(e); }
	}
	
	
	private static void handleStartupError(Throwable eIn)
	{
		System.err.printf("error starting server: '%s'\n", eIn.getMessage());
		System.exit(-1);
	}
}